<h1>MCP:AI 应用与外部工具协同的标准化协议解析</h1>
MCP解析
文章涉及到的代码已经共享至 Github 仓库,欢迎访问交流:https://github.com/GerZhang/course-mcp-tech
前置知识
在了解 MCP 之前,我们需要对两个最基础的概念有个清楚的认知。因为这两个概念是 MCP 的底层技术原理,是我们深入了解 MCP 最重要的知识点。当掌握这两个基础概念后,MCP 的奥妙就会无所遁形。
stdio
stdio,全称为standard input and out(标准输入输出)。通常会在 C 语言中,以 stdio.h 的方式出现。顾名思义,它的功能就是提供一个信息的标准通道,使得程序可以有效和其他程序进行”打交道”。
stdio 通常包含三个标准流(standard streams):
| 名称 | 全称 | 缩写 | 作用简述 | 常见用途(C/C++) |
|---|---|---|---|---|
| stdin | standard input | 标准输入 | 程序用来接收输入的地方,通常是键盘 | 比如scanf()读取键盘输入内容 |
| stdout | standard output | 标准输出 | 程序用来输出正常信息的地方,通常是屏幕 | 比如 printf打印的内容 |
| stderr | standard error | 标准错误 | 程序用来输出错误信息或警告的地方,通常也是屏幕 | 用 fprintf(stderr, …)打印错误信息 |
在操作系统,以及多种编程语言中,都遵循 stdio 这个”标准三流”模型来实现程序与外部(终端、键盘、屏幕等)之间的通信。这也就意味着,我们所编写程序的所有输入输出,其底层都通过这三个默认通信管道进行数据交互。
场景举例
我们可以借助 node.js 来模拟一个标准的 stdio 场景。
1.构建一个 node 环境
- mkdir stdio-demo npm init
2.创建 server.js,并通过 stdout 输出当前 server 的进程 id。
// server.js
process.stdout.write(process.pid + '\n')
3.在终端运行 server.js,查看输出内容
- node server.js

当我们在终端输入命令 node server.js时,我们会在终端中看到 node 运行起来的 server 进程所拥有的进程 id。这是一个习以为常的场景,但是不知你有没有好奇过,为什么终端进程可以看到其他进程的数据信息呢?
答案就在 stdio 中。因为终端进程在开启一个新进程(node)的时候,顺手监听了新进程的标准输入(stdin)与标准输出(stdout)。

我们可以修改 server.js,增加对 stdin 的处理,这样就能实现一个最基础的 AI 对话场景:
// server.js
process.stdin.on('data', (data) => {
const resp = `AI 复述: ${data.toString().trim()}`
process.stdout.write(resp + '\n')
})

当然,除了终端,我们也可以使用程序来自行实现 client 的逻辑。例如:
// client.js
import { spawn } from 'child_process'
// 启动服务端进程
const serverProcess = spawn('node', ['server.js'])
// 监听服务端进程的标准输出
serverProcess.stdout.on('data', (data) => {
console.log(data.toString().trim())
})
// 测试消息发送
const messages = [
"明月几时有?",
"把酒问青天。",
"不知天上宫阙,",
"今夕是何年。"
]
messages.forEach((message, index) => {
setTimeout(() => {
console.log(`-->:${message}`)
serverProcess.stdin.write(message + '\n')
}, index * 1000) // 每秒发送一条数据
})

JSON-RPC
JSON-RPC,一种轻量级的远程过程调用(Remote Procedure Call,简称 RPC)协议,它使用 JSON(JavaScript Object Notation) 作为数据格式,用于客户端与服务器之间的通信。一个标准 MCP 应用,其传输数据的规范,就是 JSON-RPC。
JSON-RPC 定义了一套规则,让调用者可以通过发送一个请求,来申请服务器执行对应的函数,再把结果返回给调用者。
数据格式
假设服务器端提供了两个可供外部调用的方法:
// server.js
import fs from 'fs'
export default {
// 方法 1:求和
sum({ a, b }) {
return a + b
},
// 方法 2:创建文件
createFile({ filename, content }) {
try {
fs.writeFileSync(filename, content)
return `文件 ${filename} 已创建`
} catch (err) {
return `创建文件 ${filename} 失败:${err.message}`
}
}
}
作为客户端,我们希望调用其中的求和方法,发起的请求消息格式如下:
{
"jsonrpc": "2.0",
"method": "sum",
"params": {
"a": 2,
"b": 3
},
"id": 1
}
| 字段名 | 必填 | 说明 |
|---|---|---|
| jsonrpc | 是 | 协议版本,固定为 “2.0”(目前主流都用 2.0) |
| method | 是 | 要调用的远程方法/函数名,比如 “sum” |
| params | 是 | 传给这个方法的参数,可以是数组或对象,比如 {“a”:2,”b”:3} |
| id | 是 | 请求的唯一标识符,用于匹配请求和响应,可以是数字或字符串,自行构建 |
当服务器处理完请求后,会返回类似如下的响应结果:
{
"jsonrpc": "2.0",
"result": 5,
"id": 1
}
| 字段名 | 说明 |
|---|---|
| jsonrpc | 协议版本,同样是 “2.0” |
| result | 方法执行后返回的结果,这里是 2 + 3 = 5 |
| id | 和请求中的 id一致,用于标识是哪个请求的响应 |
如果服务器在执行过程中报错,则会返回:
{
"jsonrpc": "2.0",
"error": {
"code": -32601,
"message": "Method not found"
},
"id": 1
}
| 字段 | 说明 |
|---|---|
| error | 是一个对象,包含错误详情 |
| code | 错误码,比如 -32601表示“方法未找到”(这是 JSON-RPC 标准错误码之一) |
| message | 错误的可读描述 |
| id | 和请求中的 id 对应,用来匹配是哪个请求出错了 |
场景举例
我们将上述示例构建一个标准的服务器并启动:
// util.js
import fs from 'fs'
export default {
// 方法 1:求和
sum({ a, b }) {
return a + b
},
// 方法 2:创建文件
createFile({ filename, content }) {
try {
fs.writeFileSync(filename, content)
return `文件 ${filename} 已创建`
} catch (err) {
return `创建文件 ${filename} 失败:${err.message}`
}
}
}
//=====================================================
// server.js
import utils from './utils.js'
process.stdin.on('data', (data) => {
// 解析 stdin 输入的内容
const req = JSON.parse(data)
// 提取请求消息体中指定的调用方法名
const funcName = req.method
// 提取请求消息体中调用方法所提供的参数
const params = req.params
// 在工具库中调用对应的方法并获得结果
const result = utils[funcName](params)
const resp = {
jsonrpc: '2.0',
id: req.id,
result
}
process.stdout.write(JSON.stringify(resp) + '\n')
})
MCP
MCP(Model Context Protocol,模型上下文协议)的本质,就是规定了一个应用程序之间如何通信的标准协议。
AI 大模型作为人工智能应用程序,可以使用 MCP ,连接到数据源、工具以及工作流程,从而能够访问关键信息并执行任务。
MCP 使用 JSON-RPC 来编码消息。该协议目前定义了两种用于客户端-服务器通信的标准传输机制:
- stdio,标准输入和标准输出的通信(推荐,高效、简洁、本地)
- Streamable HTTP(可远程)
协议明确标注,客户端应该在尽可能的情况下支持 stdio。
基本规范(Lifecycle)
MCP 定义了严格的生命周期:
- Initialization(初始化):功能协商和协议版本协议
- Operation(操作):正常协议通信
- Shutdown(关闭):连接的优雅终止

初始化阶段
这个阶段必须是客户端和服务器之间的第一个交互。在此阶段,客户端和服务器:
- 建立协议版本兼容性
- 交换和协商功能
- 共享实现细节
1. Initialization
客户端必须发送一个 initialize 请求来启动此阶段,该请求包括:
- 支持的协议版本
- 客户端功能
- 客户端实现信息
{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize", // 值固定
"params": {
"protocolVersion": "2025-06-18", // 协议版本需一致
"capabilities": {
"roots": {
"listChanged": true
},
"sampling": {},
"elicitation": {}
},
"clientInfo": { // 告知客户端信息
"name": "warp terminal client",
"title": "warp terminal",
"version": "1.0.0"
}
}
}
服务器必须响应其自身功能和信息:
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"protocolVersion": "2025-06-18",
"capabilities": {
"tools": {
"listChanged": true
}
},
"serverInfo": {
"name": "mcp-demo-server",
"version": "1.0.0"
}
}
}
初始化成功后,客户端必须发送 initialized 通知,以表示它已准备好开始正常操作:
{
"jsonrpc": "2.0",
"method": "notifications/initialized"
}
2. tools/list
关于 Tools 列表相关的消息结构内容,可参考:Server Features – Tools
客户端可以发送请求,来查询服务器上有哪些工具函数可以供客户端调用
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/list"
}
服务器收到请求后的响应:
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"tools": [
{
"name": "sum",
"title": "两数求和",
"description": "计算两个数字的和",
"inputSchema": {
"type": "object",
"properties": {
"a": {
"type": "number",
"description": "第一个数字"
},
"b": {
"type": "number",
"description": "第二个数字"
}
},
"required": [
"a",
"b"
]
}
},
{
"name": "createFile",
"title": "创建文件",
"description": "创建一个文本文件",
"inputSchema": {
"type": "object",
"properties": {
"filename": {
"type": "string",
"description": "文件名(如:note.txt)"
},
"content": {
"type": "string",
"description": "文件内容"
}
},
"required": [
"filename",
"content"
]
}
}
]
}
}
操作阶段
在操作阶段,客户端和服务器根据协商的能力交换消息。在符合协议版本的基础上,客户端必须仅调用协商成功的能力。调用方式使用 tools/call。
客户端调用请求:
{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "sum",
"arguments": {
"a": 2,
"b": 3
}
}
}
服务器会在调用成功后,将处理结果返回:
{
"jsonrpc": "2.0",
"id": 2,
"result": {
"content": [
{
"type": "text",
"text": "计算结果:2 + 3 = 5"
}
]
}
}
MCP Inspector
MCP Inspector 是一个用于测试和调试MCP服务器的交互式开发工具。我们可以用其来连接测试基本规范中构建的 MCP server。

MCP SDK
日常开发中,我们可以使用官方提供的 MCP SDK 来进行 MCP 相关的开发。
上述的全生命周期流程的代码用 SDK 重构后:
// server.js
import { McpServer} from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
// 创建 MCP 服务器
const server = new McpServer({
name: "mcp-sdk-server",
version: "1.0.0"
});
// 注册工具
server.registerTool("sum",
{
title: '两数求和',
description: '计算两个数字的和',
inputSchema: {
a: z.number().describe('第一个数字'),
b: z.number().describe('第二个数字')
}
},
async ({ a, b }) => ({
content: [{
type: "text",
text: String(`计算结果:${a} + ${b} = ${a + b}`)
}]
})
)
server.registerTool("createFile",
{
title: '创建文件',
description: '创建一个文本文件',
inputSchema: {
filename: z.string().describe('文件名(如:note.txt)'),
content: z.string().describe('文件内容')
}
},
async ({ filename, content }) => {
const fs = await import('fs/promises');
try {
await fs.writeFile(filename, content);
return {
content: [{ type: "text", text: `文件 "${filename}" 创建成功,内容已写入。` }]
}
} catch (err) {
return {
content: [{ type: "text", text: `创建文件 "${filename}" 失败:${err.message}` }]
}
}
}
)
// 创建 stdio 传输层,正确传入 stdin 和 stdout
const transport = new StdioServerTransport();
// 连接服务器和传输层
await server.connect(transport);
需要留意,在 tools 的相关说明中,MCP SDK 明确需要使用 zod 作为数据格式校验工具。请确保同 SDK 一同安装。
npm install @modelcontextprotocol/sdk, zod
对接 AI 应用程序
所有能与大模型交互的应用,都可以看作是 AI 应用程序。可以说,AI 应用程序是 AI 智能体的超集。

实战演练
我们可以选择 Trae 来对接上一步开发好的 MCP 服务。
{
"mcpServers": {
"MCP-SDK-服务器": {
"command": "node",
"args": [
"/Users/gerald/Develop/Projects/Study/mcp-tech/mcp-sdk-demo/server.js"
]
}
}
}

配置成功后,我们可以看到 Trae 会自动拉取 MCP 服务所支持的 tools。

现在大模型会在解决我们问题的过程中,自行从 tools 选择并调用。

对于创建文件工具的测试调用:

其他补充
除了 MCP Server 以外,还有两个核心概念需要了解:
- MCP Host:协调和管理一个或多个 MCP 客户端的 AI 应用
- MCP Client:一个维护与 MCP 服务器连接的组件,并从 MCP 服务器获取上下文供 MCP 主机使用
MCP 遵循 Client – Server 架构,其中 MCP Host——如 Claude Code 或 Trae 或 活字格 等 AI 应用——建立与一个或多个 MCP Server 的连接。MCP Host 通过为每个 MCP Server 创建一个 MCP Client 来实现这一点。每个 MCP Client 与其对应的 MCP Server 保持一对一的专用连接。
MCP 相关资源
- Model Context Protocol:MCP 的官方网站,详细介绍了 MCP 的相关说明。其中 Sepcification 需要重点关注。
- Model Context Protocol servers:这个 github 仓库是官方整理的 MCP 的参考实现,以及社区构建的服务器和额外资源的引用
场景示例代码
Github: https://github.com/GerZhang/course-mcp-tech
MCP和 FunctionCalling的区别
既然有了 MCP,那么 FunctionCalling 是不是就没有价值了?
要解答这个问题,我们还是需要回到本质上去分析。下图是一个查询天气的标准对话,使用的应用程序是 ChatGPT Desktop。

用户询问纽约的天气,ChatGPT 通过调用美国气象局的 API 查询结果并进行答复。借助于这个基础场景,我们可以看下,用户通过 AI 大模型调用外部能力获得需求结果的链路是怎么样的:

这里有两个概念,我们需要明确:
- 我们正常认知的大模型,其实是由大模型 API 和模型本身组成。OpenAI 的服务对接的也仅仅是大模型 API。模型本身与外部的所有交互,统一通过 API 进行。当然,模型本身的相关研究完全由大模型厂商负责,开发者一般都不会关注,所以一般大家默认大模型就是 API 和模型本身的聚合体。
- 我们正常理解的 AI 大模型服务,同样是由大模型和与其配套的标准服务(通常由大模型厂商提供,也可以自行开发)整合在一起的集成服务,例如这个场景所使用的 ChatGPT,用户直接对接的反而是由 OpenAI 提供的标准对外服务,GPT 仅和 OpenAI 的服务进行沟通。
在这个链路中,查询天气函数这样的具体功能,其本质就是函数,而 OpenAI 服务器,则扮演着中间人的角色,作为大模型的代理人,这个服务既负责和最终用户沟通,也负责对接和管理各种各样的工具函数。如果我们希望能够在 AI 侧做更多的工作,就需要将工作中心放在 AI 服务器的开发和管理上。
了解了调用链路后,我们结合 MCP 与 FunctionCalling 的定义,就能很好的理解二者的价值点与区别。
- FunctionCalling 就是模型调用函数的一种能力。老实说,这个能力的命名很容易造成歧义。大模型本身并不会也不能调用外部函数。大模型只能判断什么样的场景,需要调用哪些函数而已。当大模型判断出结果后,会给出调用意图。具体的调用动作会由 AI 服务服务器来执行。AI 服务器调用完成后,再将结果返回给大模型,由大模型进行二次组装并返回最终答复结果。

- MCP 本质上就是一套函数的发现和调用协议,它只作用在服务器和函数之间。 和大模型一点关系都没有,尽管这个协议名称中包含了 Modle 这个单词,但没有大模型这个协议一样可以正常使用。它提供的所有行为都在 AI 服务器上进行,旨在维护一个更加完善且丰富的上下文(context)。AI 服务器通过 MCP 来维护工具箱,以及执行内部的工具调用。然后通过 API 和大模型进行沟通。

此时,我们发现,MCP 和 FunctionCalling 并不重叠,二者分别作用在链路的两个环节,发挥的作用也各不相同。
总结
MCP 并非一项革命性技术,甚至严格来说,它本身并算是一种”技术”。尽管被称为”协议”,却更像是一种约定俗成的规范化接口标准。事实上,在 MCP 出现之前,大语言模型(LLM)与各类 AI 工具或服务之间的协作早已存在——开发者完全可以手动对接 API、定制调用逻辑、处理上下文传递,完成今天 MCP 所支持的各类任务。
但问题在于,这种”能干”往往代价高昂:每个服务都有自己的调用格式、认证方式和上下文管理逻辑,开发者需要为不同工具重复造轮子;模型与工具之间的信息传递缺乏统一语义,容易出错且难以调试;生态之间互不兼容,限制了组合创新的可能性。
MCP 的价值,正是在这些”能干但不好干”的地方体现出来。它通过定义一套轻量、通用、可扩展的协议,让 LLM 与外部服务的对接变得更简单、更一致、更可靠。它没有带来全新的能力,却显著降低了协同成本,提升了开发效率和系统稳定性。
因此,我们应当以理性务实的态度看待 MCP:它不是魔法,而是一种务实的工程共识,是一种推动行业标准化、促进生态协同的重要基础设施。理解它、善用它,才能在日益复杂的 AI 应用生态中游刃有余。
扩展链接
低代码+MCP实战三大案例,企业如何通过MCP构建专属AI智能体?(上)
低代码+MCP实战案例,企业如何通过MCP构建专属AI智能体?(下)
</div>